package com.iwown.sample4GApi.service;

import com.alibaba.fastjson2.JSON;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.iwown.sample4GApi.bean.AppConstant;
import com.iwown.sample4GApi.bean.EcgPacket;
import com.iwown.sample4GApi.protobuf.*;
import com.iwown.sample4GApi.utils.ByteUtils;
import com.iwown.sample4GApi.utils.MyDateTimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.iwown.sample4GApi.protobuf.HisHealthData.TPAMeasureType.TPA_MEASURE_TYPE_AUTO;

public class HistoryDataParser implements IDataParser {
    private static final Logger log = LoggerFactory.getLogger(HistoryDataParser.class);

    private String deviceId;
    private RedisTemplate redisTemplate;
    public HistoryDataParser() {
    }
    public HistoryDataParser(String deviceId, RedisTemplate redisTemplate) {
        this.deviceId = deviceId;
        this.redisTemplate = redisTemplate;
    }
    public final static Map<String, List<Integer>> rri_data_map = new ConcurrentHashMap<>();

    @Override
    public void DataParse(byte[] payload) {
        HisDataOuterClass.HisNotification hisNotify = null;
        try {
            hisNotify = HisDataOuterClass.HisNotification.parseFrom(payload);
        } catch (InvalidProtocolBufferException ex) {
            ex.printStackTrace();
            return;
        }
        HisDataOuterClass.HisDataType dataType = hisNotify.getType();
        //index table can be ignored at present, it's the index of history data
        if(hisNotify.hasIndexTable()){
            //data type 18 : TCM, needed when proceed TCM data
            log.info("data type of index table: %d",dataType.ordinal());
            HisDataOuterClass.HisIndexTable idxTable = hisNotify.getIndexTable();
            if(idxTable.getIndexList() != null && idxTable.getIndexList().size()>0){
                idxTable.getIndexList().stream().forEach(
                        idx -> {
                            String dt_str = MyDateTimeUtils.convert2DateTimeString(idx.getTime().getSeconds());
                            log.info(String.format("index table time:%s, seq:%d-%d",dt_str,idx.getStartSeq(),idx.getEndSeq()));
                        }
                );
            }
        }
        if(hisNotify.hasHisData()){
            HisDataOuterClass.HisData hisData = hisNotify.getHisData();
            //health data
            if(hisData.hasHealth()){
                HisHealthData.HisDataHealth healthData = hisData.getHealth();
                parseHealthData(healthData);
            }
            //ecg data
            if(hisData.hasEcg()){
                HisEcgData.HisDataECG ecgData = hisData.getEcg();
                parseEcgData(ecgData);
            }
            //rri data
            if(hisData.hasRri()){
                HisRriData.HisDataRRI rriData = hisData.getRri();
                parseRriData(rriData);
            }
            //third party device data
            if(hisData.hasThirdPartyData()){
                ThirdPartyHealthData.HisDataThirdParty thirdParty = hisData.getThirdPartyData();
                ThirdPartyHealthData.ThirdPartyDataHealth thirdPartyData = thirdParty.getDataHealth();
                parseThirdPartyData(thirdPartyData);
            }
            //continuous spo2 data
            if(hisData.hasSpo2()){
                HisSpo2Data.HisDataSpo2 spo2Data = hisData.getSpo2();
                parseSpo2Data(spo2Data);
            }
            //ppg data
            if(hisData.hasPpg()){
                HisPpgData.HisDataPPG ppgData = hisData.getPpg();
                parsePpgData(ppgData);
            }
            //gensor data
            if(hisData.hasACCelerometerData()){
                ACCelerometerData.HisACCelerometer accData = hisData.getACCelerometerData();
                parseAccelerometerData(accData);
            }
            //multi-leads ecg
            if(hisData.hasMultiLeadsECG()){
                HisMultiLeadsEcg.HisDataMultiLeadsECG multiLeadsEcg = hisData.getMultiLeadsECG();
                parseMultiLeadsEcgData(multiLeadsEcg);
            }
            //TCM data
            if(hisData.hasYYLPFE()){
                HisYylpfeData.HisDataYYLPFE yylData = hisData.getYYLPFE();
                parseYylpfeData(yylData);
            }
        }
    }

    private void parseHealthData(HisHealthData.HisDataHealth healthData){
        String data_time = MyDateTimeUtils.convert2DateTimeString(healthData.getTimeStamp().getDateTime().getSeconds());
        //sport step/distance/calorie
        if(healthData.hasPedoData()){
            HisHealthData.HisHealthPedo his_pedo = healthData.getPedoData();
            float distance = his_pedo.getDistance()*0.1F;
            float calorie = his_pedo.getCalorie()*0.1F;
            log.info(String.format("%s step-%d, distance-%f, calorie-%f",data_time,his_pedo.getStep(),distance,calorie));
        }

        // 数据存储
        EcgPacket ecgPacket = null;
        if (redisTemplate.hasKey(AppConstant.ECG_PACKET + deviceId)) {
            Map map = redisTemplate.opsForHash().entries(AppConstant.ECG_PACKET + deviceId);
            ecgPacket = JSON.parseObject(JSON.toJSONString(map), EcgPacket.class);
        } else {
            ecgPacket = new EcgPacket();
        }

        //heart rate
        if(healthData.hasHrData()){
            HisHealthData.HisHealthHr his_hr = healthData.getHrData();
            log.info(String.format("%s avg bpm-%d, max bpm-%d, min bpm-%d",data_time,his_hr.getAvgBpm(),his_hr.getMaxBpm(),his_hr.getMinBpm()));
            ecgPacket.setHeartInfo(EcgPacket.HeartInfo.builder().avg(his_hr.getAvgBpm()).max(his_hr.getMaxBpm()).min(his_hr.getMinBpm()).build());
        }
        //spo2
        if(healthData.hasBxoyData()){
            HisHealthData.HisHealthBOxy his_boxy = healthData.getBxoyData();
            log.info(String.format("%s avg boxy-%d, max boxy-%d, min boxy-%d",data_time,his_boxy.getAgvOxy(),his_boxy.getMaxOxy(),his_boxy.getMinOxy()));
            ecgPacket.setBoxyInfo(EcgPacket.BoxyInfo.builder().avg(his_boxy.getAgvOxy()).max(his_boxy.getMaxOxy()).min(his_boxy.getMinOxy()).build());
        }
        //blood pressure
        if(healthData.hasBpData()){
            HisHealthData.HisHealthBp his_bp = healthData.getBpData();
            log.info(String.format("%s sbp-%d, dbp-%d",data_time,his_bp.getSbp(),his_bp.getDbp()));
            ecgPacket.setBloodInfo(EcgPacket.BloodInfo.builder().sbp(his_bp.getSbp()).dbp(his_bp.getDbp()).build());
        }
        //fatigue data
        if(healthData.hasHrvData()){
            HisHealthData.HisHealthHrv his_hrv = healthData.getHrvData();
            int fatigue = (int)his_hrv.getFatigue();
            if(fatigue<=0){
                fatigue = (int)(Math.log((double)his_hrv.getRMSSD())*20);
            }
            log.info(String.format("%s fatigue-%d",data_time,fatigue));
        }
        //temperature
        if(healthData.hasTemperatureData()){
            HisHealthData.HisHealthTemp his_temp = healthData.getTemperatureData();
            double shellTemp = 1.0* ByteUtils.loword(his_temp.getEviBody())/100.0;
            double envTemp = 1.0*ByteUtils.hiword(his_temp.getEviBody())/100.0;
            double axillaryTemp = 1.0*ByteUtils.loword(his_temp.getEstiArm())/100.0;
            double estTemp = 1.0*ByteUtils.hiword(his_temp.getEstiArm())/100.0;
            HisHealthData.TPAMeasureType measure_type = his_temp.getType();
            if(measure_type == TPA_MEASURE_TYPE_AUTO){
                //temperature data is available
            } else {
                //temperature measure is not finished, still calculating, not available
            }
            log.info(String.format("%s env-%f, est-%f, axillary-%f, shell-%f",data_time,envTemp,estTemp,axillaryTemp,shellTemp));
            ecgPacket.setTempInfo(EcgPacket.TempInfo.builder().env(String.format("%.2f",envTemp)).est(String.format("%.2f",estTemp)).axillary(String.format("%.2f",axillaryTemp)).shell(String.format("%.2f",shellTemp)).build());
        }
        if(healthData.hasSleepData()){
            HisHealthData.HisHealthSleep sleepData = healthData.getSleepData();
            List<Integer> dataList = sleepData.getSleepDataList();
            log.info(String.format("sleep charget:%b,shutdown:%b,data count:%d,data:%s",sleepData.getCharge(),sleepData.getShutDown(),dataList.size(),dataList));
        }
        //bp hr
        if(healthData.hasBpBpmData()){
            HisHealthData.HisHealthbp_bpm bpBpmData = healthData.getBpBpmData();
            int bp_hr = bpBpmData.getBpm();
            log.info(String.format("bp hr:%d",bp_hr));
        }
        //blood potassium
        if(healthData.hasBloodPotassiumData()){
            HisHealthData.HisHealthbloodPotassium potassiumData = healthData.getBloodPotassiumData();
            int potassium = potassiumData.getBloodPotassium();
            log.info(String.format("potassium:%d",potassium));
        }
        //bioz
        if(healthData.hasBiozData()){
            HisHealthData.HisHealthBioz biozData = healthData.getBiozData();
            int r = biozData.getR();
            int x = biozData.getX();
            int bmi = biozData.getBmi();
            int fat = biozData.getFat();
            int type = biozData.getType();
            log.info(String.format("r:%d,x:%d,bmi:%d,fat:%d,type:%d",r,x,bmi,fat,type));
        }
        //blood sugar
        if(healthData.hasBloodSugarData()){
            HisHealthData.HisHealthBlood_sugar sugarData = healthData.getBloodSugarData();
            int sugar = sugarData.getBloodSugar();
            log.info(String.format("blood sugar:%d",sugar));
        }

        // 数据存储
        log.debug(String.format("deviceId:%s, data time:%s, ecg packet:%s",deviceId, data_time, JSON.toJSONString(ecgPacket)));
        redisTemplate.opsForHash().putAll(AppConstant.ECG_PACKET + deviceId, JSON.toJavaObject(JSON.toJSON(ecgPacket), Map.class));
        redisTemplate.expire(AppConstant.ECG_PACKET + deviceId, 10,  TimeUnit.MINUTES);
    }

    private void parseEcgData(HisEcgData.HisDataECG ecgData){
        String data_time = MyDateTimeUtils.convert2DateTimeString(ecgData.getTimeStamp().getDateTime().getSeconds());
        List<Integer> rawDataList = ecgData.getRawDataList();
        log.info(String.format("ecg time:%s, count:%d",data_time,rawDataList.size()));
    }

    private void parseRriData(HisRriData.HisDataRRI rriData){
        String data_time = MyDateTimeUtils.convert2DateTimeString(rriData.getTimeStamp().getDateTime().getSeconds());
        List<Integer> rawDataList = rriData.getRawDataList();
        List<Short> actualDataList = new ArrayList<>();
        for(int i=0;i<rawDataList.size();i++){
            short fval = (short)((rawDataList.get(i) >> 16) & 0x0000ffff);
            short sval = (short)(rawDataList.get(i) & 0x0000ffff);
            actualDataList.add(fval);
            actualDataList.add(sval);
        }

        log.info(String.format("rri time:%s, count:%d, actualDataList:%s",data_time,actualDataList.size(),actualDataList));
        List<Integer> collect = actualDataList.stream()
                .map(Integer::valueOf)
                .collect(Collectors.toList());
        if(rri_data_map.containsKey(deviceId)) {
            rri_data_map.get(deviceId).addAll(collect);
        } else {
            rri_data_map.put(deviceId, actualDataList.stream()
                    .map(Integer::valueOf)
                    .collect(Collectors.toList()));
        }
    }

    private void parseThirdPartyData(ThirdPartyHealthData.ThirdPartyDataHealth thirdPartyData){
        String macAddr = thirdPartyData.getMacAddr();
        //bp data
        if(thirdPartyData.hasBpData()){
            ThirdPartyHealthData.ThirdPartyHealthBp bpData = thirdPartyData.getBpData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(bpData.getTime().getDateTime().getSeconds());
            //mode 0 manual, 1 auto
            log.info(String.format("3rd party bp time:%s, sbp:%d, dbp:%d, hr:%d, pulse:%d, mode:%d",
                    data_time,bpData.getSbp(),bpData.getDbp(),bpData.getHr(),bpData.getPulse(),
                    bpData.getMODE().ordinal()));
        }
        //glu data
        if(thirdPartyData.hasGluData()){
            ThirdPartyHealthData.ThirdPartyHealthGlu gluData = thirdPartyData.getGluData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(gluData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party bloodglucose time:%s, val:%d",data_time,gluData.getGlu()));
        }
        //scale data
        if(thirdPartyData.hasScaleData()){
            ThirdPartyHealthData.ThirdPartyHealthScale scaleData = thirdPartyData.getScaleData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(scaleData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party scale time:%s, weight:%d, impedance:%d, uints:%d, body_fat_percentage:%d",
                    data_time,scaleData.getWeight(),
                    scaleData.getImpedance(),scaleData.getUints(),
                    scaleData.getBodyFatPercentage()));
        }
        //spo2 data
        if(thirdPartyData.hasSpo2Data()){
            ThirdPartyHealthData.ThirdPartyHealthSpo2 spo2Data = thirdPartyData.getSpo2Data();
            String data_time = MyDateTimeUtils.convert2DateTimeString(spo2Data.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party spo2 time:%s, spo2:%d, bpm:%d, pi:%d",
                    data_time,spo2Data.getSpo2(),spo2Data.getBpm(),spo2Data.getPi()));
        }
        //temperature data
        if(thirdPartyData.hasTempData()){
            ThirdPartyHealthData.ThirdPartyHealthTemp tempData = thirdPartyData.getTempData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(tempData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party temperature time:%s, val:%d",
                    data_time,tempData.getBodyTemp()));
        }
        //blood ketones
        if(thirdPartyData.hasBloodKetonesData()){
            ThirdPartyHealthData.ThirdPartyHealthBloodKetones bkData = thirdPartyData.getBloodKetonesData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(bkData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party blood ketones time:%s, val:%d",
                    data_time,bkData.getBloodKetones()));
        }
        //uric acid
        if(thirdPartyData.hasUricAcidData()){
            ThirdPartyHealthData.ThirdPartyHealthUricAcid uaData = thirdPartyData.getUricAcidData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(uaData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party uric acid time:%s, val:%d",
                    data_time,uaData.getUricAcid()));
        }

        if(thirdPartyData.hasFluorescenceData()){
            ThirdPartyHealthData.ThirdPartyHealthFluorescence fluorescenceData = thirdPartyData.getFluorescenceData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(fluorescenceData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party Fluorescence time:%s, val:%d",data_time,fluorescenceData.getIfValue()));
        }

        if(thirdPartyData.hasSaccharifyData()){
            ThirdPartyHealthData.ThirdPartyHealthsaccharify saccharifyData = thirdPartyData.getSaccharifyData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(saccharifyData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party Saccharify time:%s, ngsp:%d, ifcc:%d",data_time,
                    saccharifyData.getNgspValue(), saccharifyData.getIfccValue()));
        }

        if(thirdPartyData.hasBloodLipidsData()){
            ThirdPartyHealthData.ThirdPartyHealthblood_lipids blood_lipidsData = thirdPartyData.getBloodLipidsData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(blood_lipidsData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party BloodLipids time:%s, tc:%d,tg:%d,hdlc:%d,ldlc:%d,ch:%d",data_time,
                    blood_lipidsData.getTC(), blood_lipidsData.getTG(),blood_lipidsData.getHDLC(),
                    blood_lipidsData.getLDLC(), blood_lipidsData.getCH()));
        }

        if(thirdPartyData.hasUrineData()){
            ThirdPartyHealthData.ThirdPartyHealthUrine urineData = thirdPartyData.getUrineData();
            String data_time = MyDateTimeUtils.convert2DateTimeString(urineData.getTime().getDateTime().getSeconds());
            log.info(String.format("3rd party Urine time:%s, leu:%d,nit:%d,ubg:%d,pro:%d," +
                            "ph:%d,bld:%d,sg:%d,ket:%d,vc:%d,glu:%d,bil:%d,ma:%d,cr:%d,ca:%d",
                    data_time,
                    urineData.getLeu(), urineData.getNit(),urineData.getUbg(),urineData.getPro(),
                    urineData.getPh(),urineData.getBld(),urineData.getSg(),
                    urineData.getKet(),urineData.getVc(),urineData.getGlu(),
                    urineData.getBil(),urineData.getMa(),urineData.getCr(), urineData.getCa()));
        }
    }

    private void parseSpo2Data(HisSpo2Data.HisDataSpo2 spo2Data) {
        String data_time = MyDateTimeUtils.convert2DateTimeString(spo2Data.getTimeStamp().getDateTime().getSeconds());
        List<Integer> spo2List = spo2Data.getSpo2DataList();
        log.info(String.format("spo2 history time: %s",data_time));
        for(int i=0;i<spo2List.size();i++){
            int value = spo2List.get(i);
            int spo2 = (value >> 24) & 0xFF;
            int hr = (value >> 16) & 0xFF;
            int perfusion = (value >> 8) & 0xFF;
            int touch = (value) & 0xFF;
            log.info(String.format("spo2: %d, hr: %d, perfusion: %d, touch: %d",
                    spo2,hr,perfusion,touch));
        }
    }

    private void parsePpgData(HisPpgData.HisDataPPG ppgData) {
        String data_time = MyDateTimeUtils.convert2DateTimeString(ppgData.getTimeStamp().getDateTime().getSeconds());
        List<Integer> rawDataList = ppgData.getRawDataList();
        log.info(String.format("ppg history time: %s",data_time));
        List<Short> actualDataList = new ArrayList<>();
        for(int i=0;i<rawDataList.size();i++){
            short fval = (short)((rawDataList.get(i) >> 16) & 0x0000ffff);
            if(fval<0){
                fval=(short)(fval*-1);
            }
            short sval = (short)(rawDataList.get(i) & 0x0000ffff);
            if(sval<0){
                sval=(short)(sval*-1);
            }
            actualDataList.add(fval);
            actualDataList.add(sval);
            log.info(String.format("%s ppg:%d,%d",data_time,fval,sval));
        }
    }

    private void parseAccelerometerData(ACCelerometerData.HisACCelerometer accData) {
        String data_time = MyDateTimeUtils.convert2DateTimeString(accData.getTimeStamp().getDateTime().getSeconds());
        ByteString xBstr = accData.getAccX();
        List<Integer> xList = parseByteString(xBstr);

        ByteString yBstr = accData.getAccY();
        List<Integer> yList = parseByteString(yBstr);

        ByteString zBstr = accData.getAccZ();
        List<Integer> zList = parseByteString(zBstr);
        for(int i=0;i<xList.size();i++){
            log.info(String.format("%s acc x:%d,y:%d,z:%d",data_time,xList.get(i),yList.get(i),zList.get(i)));
        }
    }

    private void parseMultiLeadsEcgData(HisMultiLeadsEcg.HisDataMultiLeadsECG multiLeadsEcg){
        String data_time = MyDateTimeUtils.convert2DateTimeString(multiLeadsEcg.getTimeStamp().getDateTime().getSeconds());
        int channel = multiLeadsEcg.getNumberOfChannels();
        int single_channel_len = multiLeadsEcg.getSingleDataByteLen();
        log.info(String.format("%s channel:%d, single channel length:%d",data_time,channel,single_channel_len));
        ByteString aBstr = multiLeadsEcg.getRawData();
        byte[] buffer = aBstr.toByteArray();
        int unit_size = single_channel_len*channel;
        for(int i=unit_size;i<=buffer.length;i=i+unit_size){
            for(int j=0;j<channel;j++){
                int num=0;
                for(int k=0;k<single_channel_len;k++){
                    int pos = i-single_channel_len*(channel-j)+k;
                    int offset = 8*(single_channel_len-k-1);
                    int part = (int)(buffer[pos])<<offset;
                    num += part;
                }
                log.info(String.format("channel %d, val:%d",j+1,num));
            }
        }
    }

    private void parseYylpfeData(HisYylpfeData.HisDataYYLPFE hisYylData){
        long dataTs = hisYylData.getTimeStamp().getDateTime().getSeconds();
        ByteString bstr = hisYylData.getRawData();
        byte[] dataBytes = bstr.toByteArray();
        for (int i = 0; i < dataBytes.length - 11; i += 12) {
            long offsetTs = 0;
            int areaUp = 0, areaDown = 0, rri = 0, motion = 0;
            int step = 0;

            for (int k = 0; k < 4; k++) {
                offsetTs |= (dataBytes[i + step] << (8 * k)) & 0xFFFFFFFF;
                step++;
            }

            for (int k = 0; k < 2; k++) {
                areaUp |= (dataBytes[i + step] << (8 * k)) & 0xFFFF;
                step++;
            }

            for (int k = 0; k < 2; k++) {
                areaDown |= (dataBytes[i + step] << (8 * k)) & 0xFFFF;
                step++;
            }

            for (int k = 0; k < 2; k++) {
                rri |= (dataBytes[i + step] << (8 * k)) & 0xFFFF;
                step++;
            }

            for (int k = 0; k < 2; k++) {
                motion |= (dataBytes[i + step] << (8 * k)) & 0xFFFF;
                step++;
            }

            long rtcTs = dataTs + offsetTs / 1000;
            long utcTs = rtcTs - (8 * 3600); //suppose the timezone is utc+8
            log.info(String.format("ts:%d, area up:%d, area down:%d, rri:%d, motion:%d",utcTs,areaUp,areaDown,rri,motion));
        }
    }

    private List<Integer> parseByteString(ByteString bstr){
        List<Integer> xList = new ArrayList<>();
        byte[] xbuffer = bstr.toByteArray();
        for(int j=1;j<xbuffer.length;j=j+2){
            byte low = (byte)xbuffer[j-1];
            int high = (int)(xbuffer[j]<<8);
            int real = low+high;
            xList.add(real);
        }
        return xList;
    }


}
